﻿/*
  Copyright (c) 2011, Mikheev Rostislav
  Copyright (c) 2011, FreshFarsh team

  Steel Engine source code is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
/**
  @author Mikheev Rostislav <hacenator@gmail.com>
  @date 23.03.2011
  @brief Window manager source file
*/

#include "window_manager.h"

#include "listener.h"

#define LISTENER_CALLBACK static_cast<EventListener>(*it).callback

using namespace steel;

WindowManager::WindowManager(void):
  BasicClass(),
  _done(false),
  _antialiasing(false),
  _antialiasing_samples(0),
  _last_listener_id(0),
  _mouse_sensetive(1)
{
  _console->Info("Windows manager initialized\n");
}

WindowManager::~WindowManager() {
  ClearListeners();
}

int  WindowManager::fps() {
  return _fps;
};

int  WindowManager::width() {
  return _width;
}

int  WindowManager::height() {
  return _height;
}

float  WindowManager::mouse_sensetive() {
  return _mouse_sensetive;
}

// Set
void  WindowManager::set_mouse_sensetive(float value) {
  _mouse_sensetive = value;
}

void WindowManager::SetAntialiasingSamples(int samples) {
  if (samples != 0) {
      _antialiasing = true;
      _antialiasing_samples = samples;
  } else {
      _antialiasing = false;
  }
}

void WindowManager::OpenWindow(int width, int height, bool fullscreen) {
  // Information about the current video settings.
  const SDL_VideoInfo *info = NULL;

  // First, initialize SDL's video subsystem.
  if(SDL_Init(SDL_INIT_VIDEO) < 0)
    _console->Errorf("Video initialization failed: %s\n", SDL_GetError());

  /* Let's get some video information. */
  info = SDL_GetVideoInfo();
  if(!info)
    _console->Errorf("Video query failed: %s\n", SDL_GetError());

  _fullscreen = fullscreen;
  _width = width;
  _height = height;
  _bpp = info->vfmt->BitsPerPixel;

  // Attributes
  SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
  SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

  // Full screen anti aliasing
  if (_antialiasing) {
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, _antialiasing_samples);
  } else {
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
  }

  // Window Creation Flags
  int flags = SDL_OPENGL | (_fullscreen ? SDL_FULLSCREEN : 0);

  // caption
  SDL_WM_SetCaption("FreshFarsh", "FreshFarsh");
  SDL_ShowCursor(0);

  // video mode
  if(SDL_SetVideoMode(_width, _height, _bpp, flags ) == 0)
    _console->Errorf("Video mode set failed: %s\n", SDL_GetError());

  InitOpenGL();
  BeginMainLoop();
}

bool WindowManager::IsActive() {
  return SDL_GetAppState() & SDL_APPINPUTFOCUS;
}

bool WindowManager::IsOpened() {
  return SDL_GetAppState() & SDL_APPACTIVE;
}

Point2i WindowManager::GetMousePosition() {
  int mouse_x = 0, mouse_y = 0;
  Point2i position;

  SDL_GetMouseState(&mouse_x, &mouse_y);
  SDL_WarpMouse(_width / 2, _height / 2);

  mouse_x -= _width / 2;
  mouse_y -= _height / 2;

  position.x = mouse_x * _mouse_sensetive;
  position.y = mouse_y * _mouse_sensetive;

  return position;
}

///-----------------------------------------------------
void WindowManager::ProcessKeyboard(const SDL_KeyboardEvent * key) {
  // In debug mode exit by ESC
  #ifdef DEBUG
  if (key->keysym.sym == SDLK_ESCAPE)
    exit(2);
  #endif

  std::list<EventListener>::iterator it = _listeners.begin();

  // inform listeners
  if (key->type == SDL_KEYDOWN) {
    for (it; it != _listeners.end(); ++it) {
      if (it->event == kListenKeyDown)
        if (it->listener == kListenerFunction) {
          (reinterpret_cast<KeyDownCallback>(LISTENER_CALLBACK))(key->keysym.sym);
        } else if (it->listener == kListenerObject) {
          (reinterpret_cast<IKeyboardListener*>(LISTENER_CALLBACK))->OnKeyDown(key->keysym.sym);
        }
    }
  } else {
    for (it; it != _listeners.end(); ++it) {
      if (it->event == kListenKeyDown)
        if (it->listener == kListenerFunction) {
          (reinterpret_cast<KeyUpCallback>(LISTENER_CALLBACK))(key->keysym.sym);
        } else if (it->listener == kListenerObject) {
          (reinterpret_cast<IKeyboardListener*>(LISTENER_CALLBACK))->OnKeyUp(key->keysym.sym);
        }
    }
  }
}

///-----------------------------------------------------
void WindowManager::ProcessMouseButton(const SDL_MouseButtonEvent * button) {
  std::list<EventListener>::iterator it = _listeners.begin();

  // inform listener
  if (button->type == SDL_MOUSEBUTTONDOWN) {
    for (it; it != _listeners.end(); ++it ) {
      if (it->event == kListenMouseDown)
        if (it->listener == kListenerFunction) {
          (reinterpret_cast<MouseDownCallback>(LISTENER_CALLBACK))(button->x,
                                                            button->y,
                                                            button->which);
        } else if (it->listener == kListenerObject) {
          (reinterpret_cast<IMouseListener*>(LISTENER_CALLBACK))->OnMouseDown(button->x,
                                                                     button->y,
                                                                     button->which);
        }
    }
  } else {
    for (it; it != _listeners.end(); ++it ) {
      if (it->event == kListenMouseUp)
        if (it->listener == kListenerFunction) {
          (reinterpret_cast<MouseUpCallback>(LISTENER_CALLBACK))(button->x,
                                                          button->y,
                                                          button->which);
        } else if (it->listener == kListenerObject) {
          (reinterpret_cast<IMouseListener*>(LISTENER_CALLBACK))->OnMouseUp(button->x,
                                                                   button->y,
                                                                   button->which);
        }
    }
  }
}

void WindowManager::ProcessMouseMove(const SDL_MouseMotionEvent * move) {
  std::list<EventListener>::iterator it = _listeners.begin();

  // inform listeners
  for (it; it != _listeners.end(); ++it) {
    if (it->event == kListenMouseMove)
      if (it->listener == kListenerFunction) {
        (reinterpret_cast<MouseMoveCallback>(LISTENER_CALLBACK))(move->x, move->y);
      } else if (it->listener == kListenerObject) {
        (reinterpret_cast<IMouseListener*>(LISTENER_CALLBACK))->OnMouseMove(move->x,
                                                                   move->y);
      }
  }

}

///-----------------------------------------------------
void WindowManager::ProcessEvents() {
  SDL_Event message;

  // process all events message
  while(SDL_PollEvent(&message)) {
    switch(message.type) {
      // Keys
      case SDL_KEYDOWN:
      case SDL_KEYUP:
        ProcessKeyboard(&message.key);
      break;

      // Mouse
      case SDL_MOUSEBUTTONDOWN:
      case SDL_MOUSEBUTTONUP:
        ProcessMouseButton(&message.button);
      break;

      case SDL_MOUSEMOTION:
        ProcessMouseMove(&message.motion);
      break;

      case SDL_QUIT:
        std::list<EventListener>::iterator it = _listeners.begin();

        // inform listeners
        for (it; it != _listeners.end(); ++it ) {
          if (it->event == kListenClose)
            if (it->listener == kListenerFunction) {
              (reinterpret_cast<CloseCallback>(LISTENER_CALLBACK))();
            } else if (it->listener == kListenerObject) {
              (reinterpret_cast<IWindowListener*>(LISTENER_CALLBACK))->OnClose();
            }
        }
        exit(2);
      break;
    }
  }
}

///-----------------------------------------------------
void WindowManager::ProcessDrawing() {
  // FPS counter
  if(GetTickCount() - _last_fps >= 1000 ) {
    _last_fps = GetTickCount();
    _fps = _frames;
    _frames = 0;
  }
  _frames++;

  // Render Frame
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // listeners
  std::list<EventListener>::iterator it = _listeners.begin();

  for (it; it != _listeners.end(); ++it ) {
    if (it->event == kListenDraw)
      if (it->listener == kListenerFunction) {
        reinterpret_cast<DrawCallback>(LISTENER_CALLBACK)();
      } else if (it->listener == kListenerObject) {
        reinterpret_cast<IWindowListener*>(LISTENER_CALLBACK)->OnDraw();
      }

  }

  // Show Frame
  SDL_GL_SwapBuffers( );
}

///-----------------------------------------------------
void WindowManager::BeginMainLoop() {
  while(!_done) {
    ProcessEvents();
    ProcessDrawing();
  }
}

///-----------------------------------------------------
bool WindowManager::InitOpenGL() {
  float ratio;
  ratio = static_cast<float>(_width) / static_cast<float>(_height);

  // GLEW
  _console->Info("GLEW Initialization\n");

  GLenum err = glewInit();
  if (GLEW_OK != err) {
    /* Problem: glewInit failed, something is seriously wrong. */
    _console->Errorf("GLEW Error: %s\n", glewGetErrorString(err));
    return false;
  }
  _console->Infof("Using GLEW %s\n", glewGetString(GLEW_VERSION));

  // Initializing OpenGL
  glClearColor(79.0f / 255.0f, 190.0f / 255.0f, 218.0f / 255.0f, 1);
  glShadeModel(GL_SMOOTH);

  // Culling???
  //glCullFace(GL_BACK);
  //glFrontFace(GL_CCW);
  //glEnable(GL_CULL_FACE);

  // Viewport
  glViewport(0, 0, _width, _height);

  if (_antialiasing)
    glEnable(GL_MULTISAMPLE);

  // Projection
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  //gluPerspective( 60.0, ratio, 1.0, 1024.0 ); // Ortho2D
  //glOrtho();
  gluOrtho2D(0, _width, _height, 0);


  // inform listeners
  std::list<EventListener>::iterator it = _listeners.begin();

  for (it; it != _listeners.end(); ++it) {
    if (it->event == kListenInit)
      if (it->listener == kListenerFunction) {
        reinterpret_cast<InitCallback>(LISTENER_CALLBACK)();
      } else if (it->listener == kListenerObject) {
        reinterpret_cast<IWindowListener*>(LISTENER_CALLBACK)->OnInit();
      }
  }
}

/* events */
ListenerId WindowManager::AddListener(VariantCallback callback, ListenerEvent event) {
  EventListener * listener = new EventListener;

  listener->id = ++_last_listener_id;
  listener->callback = callback;
  listener->event = event;
  listener->listener = kListenerFunction;

  _listeners.push_back(*listener);
}

ListenerId WindowManager::AddListener(void * object, ListenerEvent event) {
  EventListener * listener = new EventListener;

  listener->id = ++_last_listener_id;

  reinterpret_cast<IListener *>(object)->IsListener();

  listener->callback = reinterpret_cast<VariantCallback>(object);

  reinterpret_cast<IListener *>(listener->callback)->IsListener();

  listener->event = event;
  listener->listener = kListenerObject;

  _listeners.push_back(*listener);
}

bool WindowManager::RemoveListener(ListenerId id) {
  std::list<EventListener>::iterator it = _listeners.begin();

  for (it; it != _listeners.end(); it++)
    if (it->id == id) {
      //delete static_cast<EventListener>(*it);

      _listeners.erase(it);
      return true;
    }

  return false;
}

void WindowManager::ClearListeners() {
  std::list<EventListener>::iterator it;

  it = _listeners.begin();

  //delete static_cast<EventListener>(*it);
  _listeners.erase(it);

  //TODO: please test what here are removed all listeners
}

#undef LISTENER_CALLBACK
